package ar.uba.fi.tonyvaliente.tree;

import java.util.NoSuchElementException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ar.uba.fi.tonyvaliente.files.File;
import ar.uba.fi.tonyvaliente.files.FileException;
import ar.uba.fi.tonyvaliente.files.Persistor;
import ar.uba.fi.tonyvaliente.files.RecordPosition;

/**
 * Implementación en memoria del árbol.
 * 
 * @author santiago
 *
 */
public class DiskBTree<K extends Comparable<K>, V> extends BTree<K, V> {

	private static Log log = LogFactory.getLog(DiskBTree.class);
	
	File file;
	BTreeFileWrapper<K,V> wrapper;

	public DiskBTree(Integer elementosPorNodo, String pathName,
			Persistor<K> keyPersistor, Persistor<V> valuePersistor) throws BTreeException {
		
		super();
		try {
			this.file = File.create(pathName, 4096);
			this.wrapper = new BTreeFileWrapper<K,V>(file, keyPersistor, valuePersistor);
		} catch (FileException e) {
			log.fatal("Error creando el BTree", e);
			throw new BTreeException("Error creando el BTree", e);
		}

		try{
			BReference<K, V> reference = this.createReference();
			this.setElementosPorNodo(elementosPorNodo);
			reference.setNode(new BTreeNode<K, V>(this, elementosPorNodo));
			this.setRoot(reference);
		}catch(BTreeException e){
			log.fatal("Se produjo un error creando la raiz del árbol", e);
			throw e;
		}
	}

	public DiskBTree(String pathName,boolean readOnly,
			Persistor<K> keyPersistor, Persistor<V> valuePersistor) throws BTreeException {
		
		super();
		try {
			this.file = File.open(pathName, readOnly);
			this.wrapper = new BTreeFileWrapper<K,V>(file, keyPersistor, valuePersistor);
		} catch (FileException e) {
			log.fatal("Error abriendo el BTree" , e);
			throw new BTreeException("Error abriendo el BTree, path: " + pathName, e);
		}

		try{
			this.getRoot();
		}catch(BTreeException e){
			log.fatal("Se produjo un error obteniendo la raiz del árbol", e);
			throw e;
		}
	}


	/* (non-Javadoc)
	 * @see ar.uba.fi.datos.tonyvaliente.tree.BTree#createReference()
	 */
	@Override
	protected BReference<K, V> createReference() {

		BReference<K, V> reference = new DiskBReference<K, V>(this.wrapper);
		return reference;
	}

	/* (non-Javadoc)
	 * @see ar.uba.fi.datos.tonyvaliente.tree.BTree#get(java.lang.Comparable)
	 */
	@Override
	public V get(K key) throws BTreeException, NoSuchElementException {
		return this.getRoot().getNode().get(key);
	}



	@Override
	protected BReference<K, V> getRoot() throws BTreeException{
		if(super.getRoot() == null){
			
			try {
				DiskBReference<K,V> rootReference = new DiskBReference<K, V>(this.wrapper);
				RecordPosition rootPosition;
				rootPosition = this.wrapper.getRootNodePosition();
				BTreeNode<K, V> rootNode = this.wrapper.getNode(rootPosition.getBlockNumber(), rootPosition.getOffset());
				rootReference.setNode(rootNode);
				rootReference.setRecordPosition(rootPosition);
				super.setRoot(rootReference);
			} catch (BTreeException e) {
				log.fatal("Error obteniendo la raiz del árbol", e);
				throw e;
			}
		}
		return super.getRoot();
	}



	@Override
	protected void setRoot(BReference<K, V> root) throws BTreeException{
		super.setRoot(root);
		DiskBReference<K, V> diskRoot = (DiskBReference<K, V>)root;
		try {
			if(diskRoot.getRecordPosition() == null){
				RecordPosition recordPosition = this.wrapper.appendNode(diskRoot.getNode());
				this.wrapper.setRootNodePosition(recordPosition);
				diskRoot.setRecordPosition(recordPosition);
			}else{
				this.wrapper.setRootNodePosition(diskRoot.getRecordPosition());
				diskRoot.updateNode();
			}
		} catch (BTreeException e) {
			log.fatal("Error seteando la raiz del árbol", e);
			throw e;
		}
		
	}



	@Override
	public void close() throws BTreeException{

		this.wrapper.close();
		
	}

}
